package com.imooc.security.core.validate.code;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;

import com.imooc.security.core.properties.SecurityConstants;
import com.imooc.security.core.properties.SecurityProperties;
import com.imooc.security.core.validate.code.exception.ValidateCodeException;

/**
 * 校验码校验过滤器
 * @author JZH
 *
 */
@Component("validateCodeFilter")
public class ValidateCodeFilter  extends OncePerRequestFilter implements InitializingBean {

	/**
	 * 系统配置信息
	 */
	@Autowired
	private SecurityProperties securityProperties;
	
	@Autowired
	private AuthenticationFailureHandler authenticationFailureHandler;
	
	/**
	 * 存放所有需要校验验证码的url
	 */
	private Map<String, ValidateCodeType> urlMap = new HashMap<>();
	
	/**
	 * 验证请求url与配置的url是否匹配的工具类
	 */
	private AntPathMatcher pathMatcher = new AntPathMatcher();
	
	/**
	 * 校验码处理器支持类，用于根据校验码类型获取对应的处理器
	 */
	@Autowired
	private ValidateCodeProcessorHolder validateCodeProcessorHolder;
	
	/**
	 * InitializingBean接口规范的初始化方法
	 */
	@Override
	public void afterPropertiesSet() throws ServletException {
		// TODO Auto-generated method stub
		super.afterPropertiesSet();
		
		urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM, ValidateCodeType.IMAGE);
		addUrlsToMap(securityProperties.getCode().getImage().getUrls(), ValidateCodeType.IMAGE);
		
		urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, ValidateCodeType.SMS);
		addUrlsToMap(securityProperties.getCode().getSms().getUrls(), ValidateCodeType.SMS);
	}
	
	
	
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		ValidateCodeType codeType = getValidateCodeType(request); //若无需校验，则codeType为null
		if(null != codeType) {
			logger.info("校验请求(" + request.getRequestURI() + ")中的验证码,验证码类型" + codeType);
			try {
				validateCodeProcessorHolder.findValidateCodeProcessor(codeType)
						.validate(new ServletWebRequest(request, response));
				logger.info("验证码校验通过");
			} catch (ValidateCodeException exception) {
				authenticationFailureHandler.onAuthenticationFailure(request, response, exception);
				return;
			}
		}
		filterChain.doFilter(request, response);//别忘了这个...
	}
	
	/*
	 * - - - - - - - - - - - - 自身定义的工具方法
	 */
	
	/**
	 * 将用户自定义的校验url放入map中
	 * @param urlString
	 * @param type
	 */
	private void addUrlsToMap(String urlString, ValidateCodeType type) {
		String[] urlArray = StringUtils.splitByWholeSeparatorPreserveAllTokens(urlString, ",");
		for(String url : urlArray) {
			urlMap.put(url, type);
		}
	}
	
	/**
	 * 获取校验码的类型，如果当前请求不需要校验，则返回null
	 * 
	 * @param request
	 * @return
	 */
	private ValidateCodeType getValidateCodeType(HttpServletRequest request) {
		ValidateCodeType result = null;
		if (!StringUtils.equalsIgnoreCase(request.getMethod(), "get")) {
			Set<String> urls = urlMap.keySet();
			for (String url : urls) {
				if (pathMatcher.match(url, request.getRequestURI())) {
					result = urlMap.get(url);
				}
			}
		}
		return result;
	}

	/*
	 * - - - - - - - - - - - - getter、setter 方法
	 */
	
	public SecurityProperties getSecurityProperties() {
		return securityProperties;
	}

	public void setSecurityProperties(SecurityProperties securityProperties) {
		this.securityProperties = securityProperties;
	}

	public AuthenticationFailureHandler getAuthenticationFailureHandler() {
		return authenticationFailureHandler;
	}

	public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
		this.authenticationFailureHandler = authenticationFailureHandler;
	}

	public ValidateCodeProcessorHolder getValidateCodeProcessorHolder() {
		return validateCodeProcessorHolder;
	}

	public void setValidateCodeProcessorHolder(ValidateCodeProcessorHolder validateCodeProcessorHolder) {
		this.validateCodeProcessorHolder = validateCodeProcessorHolder;
	}

}
